Hey there!
Since the story section of our game is going to be entirely first person based, I want to add the ability
for the player's torso to tilt towards the direction of their camera, whether they are facing up or
down.
This will allow us to easily move any tools that the players are holding in the direction of their camera's
tilt.
So inside of the spawn, I've added this part here in the workspace with a light inside of it, so we
can easily see the player's character.
And if we go and play test.
And we go to our character here.
There are these different constraints that connect the different body parts to each other.
So for example, there's a motor six d constraint that connects our arms to the torso, a constraint
that connects the upper torso to the lower torso, the legs to the lower torso, and things like that.
So if we go into our character and we go to something like our upper torso, there is a motor six d
constraint in here.
And it contains two properties, one keyframe here and one keyframe here.
Now these two keyframes represent the different offsets for part zero and part one.
So C zero is the offset keyframe for part zero, and C one is the offset for part one.
So these two different parts have their own keyframe.
And these keyframes get added on to those keyframes to, you know, create an offset.
So if I were to reset this to be something like a zero, as you can see, my torso moves down.
But if I apply that -0.8 offset again, my torso returns back to its original position.
Now, using these different offsets, we can actually rotate our player's torso in the direction of
our player's camera.
So what we could do is we could get the direction of where our camera is facing.
So for example, if our camera is facing downwards, then we can have our torso rotate in the direction
where our camera is facing downwards and rotate it upwards when our camera is facing upwards.
So in order to do this, we have a script inside of starter character scripts, which is called Torso
Tilt.
And inside of here we're going to need a few variables.
One is going to be the camera of our player.
We're going to need the character, which you can do by either using the player service to get the local
character, or another way you could do it, because in the previous scripts we showed it by doing,
uh, player dot character.
But since this script is going to end up inside of our character, you could also do script dot parent
and this will give us the character as well.
And then from this point we can get the root part of our character as well.
So we do character and we'll wait for the child humanoid root part.
And then we're also going to get the upper torso of our character as well, which is character.
Wait for child upper torso.
Now, another thing we want to get is that motor 60 constraint called waste.
So this variable will be called waste.
And inside of our upper torso we're going to wait for this waste constraint.
And something that's very important is that we need to store the Y offset of the first C frame that
is stored inside of that constraint.
We need to keep that Y offset.
So that way our torso doesn't move downwards to a different position.
So we'll create a variable called y offset.
And that will be equal to the waist dot c zero dot y.
And we can also denote this as a motor six d.
And we need to make sure we store that y offset that is applied on this constraint here.
Now we're actually going to also need to grab a service.
So I'm going to copy this.
And I'll make a section up here for services.
And the only service that we're going to need is the run service.
So run service is equal to game get service run service.
And this is because we're going to be updating the rotation of our player every single frame.
So in the run service we're going to connect to the render step event.
And what this is going to do is, is it going to calculate the rotation of our camera relative to our
root part.
And then based on that, it's going to rotate the waist constraint that we have inside of our upper
torso.
Now, since this was going to be firing every single frame and there are several global functions we're
going to be using in here, it's better for us to store these global functions in local memory.
That way it's more performant when we're executing this function in render step.
So the global functions we're going to need is for creating a new key frame.
So we'll call it CF new.
Another one is for creating a new key frame with some angles.
So that's CF angles.
And then we'll need a reference to a function in the math library called a sine which stands for arc
sine.
So we'll make those references.
So C frame dot new C frame dot angles.
And then math dot a sine.
Now what we're going to do inside of this handler is first make sure that waste actually exists.
Because if our character dies and this um, constraint here gets destroyed, we're going to end up with
errors.
So if waste does not exist, then we're just going to return.
Otherwise what we're going to do is we're going to get the direction that our camera is facing.
So we're going to make a variable called cam direction.
And what we could do is we can get the camera dot c frame.
And we want to make this camera C frame.
Uh, we want to calculate the C frame relative to the C frame of our root part.
So basically we want the c frame of our root part to act as the origin when we calculate the camera
C frame.
Because this C frame is going to give us a bunch of numbers based on the game's global, um, origin,
which is 000.
But we want to use the root part of our character as the origin for the C frame.
How can we do that?
Well, what we could do is we could reference our root part, and then we can get the c frame of our
root part.
And the C frame has a function called two object space, which allows us to calculate a c frame using
this c frame as the origin point.
And that means we can pass our, um, camera C frame in here.
And then to get the direction that this camera is facing based on the root part as the origin of the
C frame, we can get the look vector.
Now using this look vector, we can easily just grab the y component of it.
And for example, if the y component of our look vector is one, that means our player is looking directly
upwards.
If the y component is negative one, that means our player is looking directly downwards.
Now, I don't want my player's torso to be able to look directly upwards and directly downwards, because
that means our torso will be completely parallel with the floor and that will look very weird.
So what I'm going to do is I'm going to restrict the ability for the player's, uh, torso rotation
to extend past a certain point.
So, for example, if the camera direction dot y, if this becomes greater than a value like 0.4, then
we do not want to rotate our torso any further.
Same thing goes for if we want to look down too far.
So if the cam direction dot y becomes less than or equal to -0.7, then we want to stop it there as
well.
We don't want to rotate further than where our camera is looking down.
Otherwise we'll apply the rotation to our waist constraint.
So what we're going to do for example in this section is we're going to get our waist motor 60 constraint.
And we're going to get the key frame offset for the first part.
And what we're going to do is we're going to set it equal to itself.
But then we're going to use the lerp function or the linear interpolation function on the key frame.
So we're going to create a new key frame here.
And this key frame is going to be zero.
But it's going to keep our y offset and then zero for the other position.
So the key frame here is the exact same as the key frame that is stored in C zero.
Except we're going to create a different rotation for it.
So instead of having a rotation of zero zero what we're going to do is we're going to create a new key
frame with a different rotation.
And what we have to do here is we're going to have to use our a sine function.
Now what is a sign?
Well, again, a sign stands for arc sign.
And this is the inverse function to the regular sign function in trigonometry.
Now I'm not going to be explaining trigonometry in this lecture because that would probably be longer
than this entire course.
But basically in algebra when you have a function like y is equal to x, blah, blah, blah, if you
wanted to try to find the inverse of that function, you would have to swap the positions of x and y
and then solve for y.
Unfortunately, that becomes a little bit more complicated with the sine function, which is why the
inverse or arc sine function exists.
Now, most people have probably heard of the sine function because it generates the popular sine wave
pattern, where you have a wave going up and down, repeating itself over and over for infinity, stretching
across the x axis.
Now the inverse sine function does the exact same thing.
However, instead of the sine wave going along the x axis, the sine wave goes along the y axis.
Now we need to use this arc sine function in order to get the rotation in radians that we need to apply
on the x axis for our waste constraint here.
So what we need to do is we need to pass our cam direction dot y value.
Now because in this if statement section we want to limit this from going beyond 0.4.
Then instead we can replace this value here with 0.4.
And again this is representing our look vector value for the y direction.
And passing this number to the a sine function is going to return back to us a number that represents
the rotation on the x axis in radians.
And that means we don't have to rotate this on the y axis, and we don't have to rotate this on the
z axis.
And then we want to do basically the exact same thing here as well.
But instead we want to limit the number here.
To -0.7.
And then for this last section, if our camera direction is between these two values, then we can do
basically the exact same thing, but instead just pass the cam direction dot y component.
So what this is doing right here is that it's creating a new C frame that has the exact same position
as our C0C frame, but it is creating a rotation on that C frame based on the camera's direction or
aka which direction in the y axis is our camera facing.
So if our camera is facing downwards, then this function is going to give us a value that represents
the rotation on the x axis to rotate our waist to face downwards.
Otherwise, if our camera is facing upwards, then it's going to give us a value that represents the
rotation on the x axis to make the torso look upwards.
Now, one more value that we have to pass to the lerp function is an alpha number, and this alpha number
represents um basically the percentage between the two different C frames.
So we have our regular way C frame with no rotation.
And we have this new C frame with this new rotation that we've applied on the x axis.
And the alpha represents what percentage of the way we want to make it to this new rotation.
So if I pass the number like 0.5 then it would go halfway to this rotation.
So basically the value that gets returned from this assign function will be cut in half.
Now, since this function is going to be executing every single frame, that means our way C frame is
slowly going to move to this new position halfway each time this gets executed.
And I'll show you what I mean.
So if I set this number to something very low, like 0.01, and I copy this and do it for the same thing
on each of these lerp functions.
And I go back to test our game here.
What I'm going to do is I'm going to go to my player's character, and I'm going to go down to my upper
torso and we're going to look at our waist motor.
Six d constraint.
Now, as you can already see, our orientation is changing here to match where our camera is facing.
So if I were to make my camera look upwards, as you can see, my character is moving very slowly upwards.
And as you can see, this orientation value is continuing to increase and it keeps getting slower and
slower as each second goes by.
Now the reason for this is because we are linearly interpolating the key frame to this new rotation.
But we're only moving 1% each time.
And as we keep updating this C0, um, when this function gets called again, it'll get that new rotation
value and again try to move it 1% of the way to this new value.
So it's going to keep moving.
As you can see, it's still slowly going up until eventually it reaches the new value and now it stops.
But basically it makes our rotation of the torso very slow.
Now, if you wanted to remove this kind of animation style effect at all, then you would just replace
this number with one, which means you are setting the C frame and the C zero property to a new C frame,
which is basically the C frame that we passed here because it stands for 100%, or you're moving 100%
of the way there.
So if we test this again.
And if we rotate the player downwards.
See?
It instantly moves to the new position, right?
There's no there's no delay at all for moving to the new position, because we are interpolating the
key frame instantly to the new position.
Now, if you wanted to make this look more animated, we could of course reduce this number.
So I found a number of like 0.25 to actually look quite nice.
So that means every single time this function gets executed, uh, our rotation is being applied one
fourth of the way, or 25% of the way each time this gets executed.
So basically, what I want you to imagine is, let's say we have a rotation of zero, zero and zero,
and we want to make a rotation of like let's say 800.
Right.
Well, through the first iteration or when this function first gets executed, it's going to look at
the difference between the key frame and this key frame which is eight.
Right.
And it's going to make the key frame be 0.25% of the way to eight, which means we would have a new
value of 200.
So now our C zero is updated to 200.
And then this function is going to get executed again.
And now it's going to look.
At our value of two zero 0 to 8 zero zero.
And it's going to be like, okay, what is 25% of the way to 800 when our starting value is two?
So this is not applying the rotation in a linear fashion.
Instead, it's kind of applying the rotation in a curve fashion where it starts fast at the beginning,
but then it begins to slow down, which gives us a nice kind of animated effect when we are applying
the rotation on our waist constraint.
So when we test this out here, when we rotate upwards, we can see there is a very slight delay and
it looks nice and animated.
So when I look all the way down, as you can see, we reach that barrier where we can have the character
no longer look down any further because of that constraint we added.
And then when I look upwards, as you can see, our character stops and no longer moves upwards anymore,
right?
Now, let's say I decided to get rid of these conditions and apply no constraint at all to our waist
keyframe.
So let's say I were to comment this out.
And instead.
I were to just apply, you know, this new rotation to the C frame, then there's going to be no limit
on how far we can look down and how far we can look up.
So that means because I got rid of those limits, now I can look all the way up.
And as you can see, my torso is completely rotated incorrectly.
And that means I can also look all the way down in my torso is rotated all the way down.
Now, we don't want our torso to rotate this much, which is why we've added these limits here onto
how far the torso can actually rotate.
And of course, you can adjust these values to your liking if you want the torso to be able to rotate
a little bit more upwards, then you can increase this value to something like maybe 0.5 or 0.6.
And if you want the torso to be able to rotate more downwards, then of course you can increase this
number as well to like -0.8 or -0.9, something like that.
But I found the values of 0.4 and -0.7 respectively, to be pretty good for what we're doing in our
game.
Otherwise, that's how we can use trigonometry to apply rotation on our player's torso.
Now, I don't really expect you to understand trigonometry unless you've already known trigonometry
or you've taken a math class on trigonometry.
But otherwise, all you need to know is that this is just some simple math that we're calculating to
linearly interpolate our C frame to a new rotation based on the look vector of our camera.
So I'll see you in the next lecture.